package com.mxgraph.swing.handler;

import com.mxgraph.swing.mxGraphComponent;
import com.mxgraph.swing.util.mxMouseAdapter;
import com.mxgraph.util.*;
import com.mxgraph.util.mxEventSource.mxIEventListener;
import com.mxgraph.view.mxCellState;

import javax.swing.*;
import java.awt.*;
import java.awt.event.MouseEvent;

/**
 * Basic example of implementing a handler for rotation. This can be used as follows:
 * <p>
 * new mxRotationHandler(graphComponent)
 * <p>
 * Note that the Java core does actually not support rotation for the selection handles,
 * perimeter points etc. Feel free to contribute a fix!
 */
public class mxRotationHandler extends mxMouseAdapter {
    /**
     * The constant ROTATE_ICON.
     */
    public static ImageIcon ROTATE_ICON = null;
    /**
     *
     */
    private static double PI4 = Math.PI / 4;

    /**
     * Loads the collapse and expand icons.
     */
    static {
        ROTATE_ICON = new ImageIcon(
                mxRotationHandler.class
                        .getResource("/com/mxgraph/swing/images/rotate.gif"));
    }

    /**
     * Reference to the enclosing graph component.
     */
    protected mxGraphComponent graphComponent;

    /**
     * Specifies if this handler is enabled. Default is true.
     */
    protected boolean enabled = true;

    /**
     * The Handle.
     */
    protected JComponent handle;

    /**
     * The Current state.
     */
    protected mxCellState currentState;

    /**
     * The Initial angle.
     */
    protected double initialAngle;

    /**
     * The Current angle.
     */
    protected double currentAngle;

    /**
     * The First.
     */
    protected Point first;

    /**
     * Constructs a new rotation handler.
     *
     * @param graphComponent the graph component
     */
    public mxRotationHandler(mxGraphComponent graphComponent) {
        this.graphComponent = graphComponent;
        graphComponent.addMouseListener(this);
        handle = createHandle();

        // Installs the paint handler
        graphComponent.addListener(mxEvent.AFTER_PAINT, new mxIEventListener() {
            public void invoke(Object sender, mxEventObject evt) {
                Graphics g = (Graphics) evt.getProperty("g");
                paint(g);
            }
        });

        // Listens to all mouse events on the rendering control
        graphComponent.getGraphControl().addMouseListener(this);
        graphComponent.getGraphControl().addMouseMotionListener(this);

        // Needs to catch events because these are consumed
        handle.addMouseListener(this);
        handle.addMouseMotionListener(this);
    }

    /**
     * Gets graph component.
     *
     * @return the graph component
     */
    public mxGraphComponent getGraphComponent() {
        return graphComponent;
    }

    /**
     * Is enabled boolean.
     *
     * @return the boolean
     */
    public boolean isEnabled() {
        return enabled;
    }

    /**
     * Sets enabled.
     *
     * @param value the value
     */
    public void setEnabled(boolean value) {
        enabled = value;
    }

    /**
     * Create handle j component.
     *
     * @return the j component
     */
    protected JComponent createHandle() {
        JLabel label = new JLabel(ROTATE_ICON);
        label.setSize(ROTATE_ICON.getIconWidth(), ROTATE_ICON.getIconHeight());
        label.setOpaque(false);

        return label;
    }

    /**
     * Is state handled boolean.
     *
     * @param state the state
     * @return the boolean
     */
    public boolean isStateHandled(mxCellState state) {
        return graphComponent.getGraph().getModel().isVertex(state.getCell());
    }

    /**
     *
     */
    public void mousePressed(MouseEvent e) {
        if (currentState != null && handle.getParent() != null
                && e.getSource() == handle /* mouse hits handle */) {
            start(e);
            e.consume();
        }
    }

    /**
     * Start.
     *
     * @param e the e
     */
    public void start(MouseEvent e) {
        initialAngle = mxUtils.getDouble(currentState.getStyle(),
                mxConstants.STYLE_ROTATION) * mxConstants.RAD_PER_DEG;
        currentAngle = initialAngle;
        first = SwingUtilities.convertPoint(e.getComponent(), e.getPoint(),
                graphComponent.getGraphControl());

        if (!graphComponent.getGraph().isCellSelected(currentState.getCell())) {
            graphComponent.selectCellForEvent(currentState.getCell(), e);
        }
    }

    /**
     *
     */
    public void mouseMoved(MouseEvent e) {
        if (graphComponent.isEnabled() && isEnabled()) {
            if (handle.getParent() != null && e.getSource() == handle /* mouse hits handle */) {
                graphComponent.getGraphControl().setCursor(
                        new Cursor(Cursor.HAND_CURSOR));
                e.consume();
            } else if (currentState == null
                    || !currentState.getRectangle().contains(e.getPoint())) {
                mxCellState eventState = graphComponent
                        .getGraph()
                        .getView()
                        .getState(
                                graphComponent.getCellAt(e.getX(), e.getY(),
                                        false));

                mxCellState state = null;

                if (eventState != null && isStateHandled(eventState)) {
                    state = eventState;
                }

                if (currentState != state) {
                    currentState = state;

                    if (currentState == null && handle.getParent() != null) {
                        handle.setVisible(false);
                        handle.getParent().remove(handle);
                    } else if (currentState != null) {
                        if (handle.getParent() == null) {
                            // Adds component for rendering the handles (preview is separate)
                            graphComponent.getGraphControl().add(handle, 0);
                            handle.setVisible(true);
                        }

                        handle.setLocation(
                                (int) (currentState.getX()
                                        + currentState.getWidth()
                                        - handle.getWidth() - 4),
                                (int) (currentState.getY()
                                        + currentState.getHeight()
                                        - handle.getWidth() - 4));
                    }
                }
            }
        }
    }

    /**
     *
     */
    public void mouseDragged(MouseEvent e) {
        if (graphComponent.isEnabled() && isEnabled() && !e.isConsumed()
                && first != null) {
            mxRectangle dirty = mxUtils.getBoundingBox(currentState,
                    currentAngle * mxConstants.DEG_PER_RAD);
            Point pt = SwingUtilities.convertPoint(e.getComponent(),
                    e.getPoint(), graphComponent.getGraphControl());

            double cx = currentState.getCenterX();
            double cy = currentState.getCenterY();
            double dx = pt.getX() - cx;
            double dy = pt.getY() - cy;
            double c = Math.sqrt(dx * dx + dy * dy);

            currentAngle = ((pt.getX() > cx) ? -1 : 1) * Math.acos(dy / c)
                    + PI4 + initialAngle;

            dirty.add(mxUtils.getBoundingBox(currentState, currentAngle
                    * mxConstants.DEG_PER_RAD));
            dirty.grow(1);

            // TODO: Compute dirty rectangle and repaint
            graphComponent.getGraphControl().repaint(dirty.getRectangle());
            e.consume();
        } else if (handle.getParent() != null) {
            handle.getParent().remove(handle);
        }
    }

    /**
     *
     */
    public void mouseReleased(MouseEvent e) {
        if (graphComponent.isEnabled() && isEnabled() && !e.isConsumed()
                && first != null) {
            double deg = 0;
            Object cell = null;

            if (currentState != null) {
                cell = currentState.getCell();
				/*deg = mxUtils.getDouble(currentState.getStyle(),
						mxConstants.STYLE_ROTATION);*/
            }

            deg += currentAngle * mxConstants.DEG_PER_RAD;
            boolean willExecute = cell != null && first != null;

            // TODO: Call reset before execute in all handlers that
            // offer an execute method
            reset();

            if (graphComponent.isEnabled() && isEnabled() && !e.isConsumed()
                    && willExecute) {
                graphComponent.getGraph().setCellStyles(
                        mxConstants.STYLE_ROTATION, String.valueOf(deg),
                        new Object[]{cell});

                graphComponent.getGraphControl().repaint();

                e.consume();
            }
        }

        currentState = null;
    }

    /**
     * Reset.
     */
    public void reset() {
        if (handle.getParent() != null) {
            handle.getParent().remove(handle);
        }

        mxRectangle dirty = null;

        if (currentState != null && first != null) {
            dirty = mxUtils.getBoundingBox(currentState, currentAngle
                    * mxConstants.DEG_PER_RAD);
            dirty.grow(1);
        }

        currentState = null;
        currentAngle = 0;
        first = null;

        if (dirty != null) {
            graphComponent.getGraphControl().repaint(dirty.getRectangle());
        }
    }

    /**
     * Paint.
     *
     * @param g the g
     */
    public void paint(Graphics g) {
        if (currentState != null && first != null) {
            Rectangle rect = currentState.getRectangle();
            double deg = currentAngle * mxConstants.DEG_PER_RAD;

            if (deg != 0) {
                ((Graphics2D) g).rotate(Math.toRadians(deg),
                        currentState.getCenterX(), currentState.getCenterY());
            }

            mxUtils.setAntiAlias((Graphics2D) g, true, false);
            g.drawRect(rect.x, rect.y, rect.width, rect.height);
        }
    }

}
